package com.db.stexchem.server;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.TreeMap;

import com.db.stexchem.message.Order;

public class OpenBook {

  private final Queue<Order>                 bestMarketPriceSellOrders = new LinkedList<>();
  private final Queue<Order>                 bestMarketPriceBuyOrders  = new LinkedList<>();
  private final TreeMap<Float, Queue<Order>> sellOrders                = new TreeMap<Float, Queue<Order>>();
  private final TreeMap<Float, Queue<Order>> buyOrders                 = new TreeMap<Float, Queue<Order>>();

  public OpenBook() {
  }
  
  public List<Match> add(Order order) {
    switch (order.getOperation()) {
    case SELL:
      return addSellOrder(order);
    case BUY:
    default:
      return addBuyOrder(order);
    }
  }
  
  private List<Match> addBuyOrder(Order order) {
    if (order.getPrice() != null) {
      return addLimitBuyOrder(order);
    } else {
      return addMarketBuyOrder(order);
    }
  }
  
  private List<Match> addMarketBuyOrder(Order order) {
    List<Match> ret = new ArrayList<>();
    while (!sellOrders.isEmpty() && sellOrders.firstKey() != null && order.getNShares() > 0) {
      Queue<Order> orderQueue = sellOrders.firstEntry().getValue();
      while (!orderQueue.isEmpty() && order.getNShares() > 0) {
        ret.add(Match.match(
            order,
            orderQueue.element(),
            orderQueue.element().getPrice(),
            Math.min(order.getNShares(), orderQueue.element()
                .getNShares())));
        if (orderQueue.element().getNShares() == 0) {
          orderQueue.remove();
        }
      }
      if (orderQueue.isEmpty()) {
        sellOrders.remove(sellOrders.firstKey());
      }
    }
    if (order.getNShares() > 0) {
      bestMarketPriceBuyOrders.add(order);
    }
    return ret;
  }

  private List<Match> addLimitBuyOrder(Order order) {
    float price = order.getPrice();
    List<Match> ret = new ArrayList<>();
    // first, match with best market price orders
    while (!bestMarketPriceSellOrders.isEmpty() && order.getNShares() > 0) {
      ret.add(Match.match(
          order,
          bestMarketPriceSellOrders.element(),
          price,
          Math.min(order.getNShares(), bestMarketPriceSellOrders.element()
              .getNShares())));
      if (bestMarketPriceSellOrders.element().getNShares() == 0) {
        bestMarketPriceSellOrders.remove();
      }
    }
    // then match with limit orders
    while (!sellOrders.isEmpty() && sellOrders.firstKey() != null && sellOrders.firstKey() <= price
        && order.getNShares() > 0) {
      Queue<Order> orderQueue = sellOrders.firstEntry().getValue();
      while (!orderQueue.isEmpty() && order.getNShares() > 0) {
        ret.add(Match.match(
            order,
            orderQueue.element(),
            orderQueue.element().getPrice(),
            Math.min(order.getNShares(), orderQueue.element()
                .getNShares())));
        if (orderQueue.element().getNShares() == 0) {
          orderQueue.remove();
        }
      }
      if (orderQueue.isEmpty()) {
        sellOrders.remove(sellOrders.firstKey());
      }
    }
    if (order.getNShares() > 0) {
      Queue<Order> orderQueue = buyOrders.get(price);
      if (orderQueue != null) {
        orderQueue.add(order);
      } else {
        orderQueue = new LinkedList<>();
        orderQueue.add(order);
        buyOrders.put(price, orderQueue);
      }
    }
    return ret;
  }

  private List<Match> addSellOrder(Order order) {
    if (order.getPrice() != null) {
      return addLimitSellOrder(order);
    } else {
      return addMarketSellOrder(order);
    }
  }

  private List<Match> addMarketSellOrder(Order order) {
    List<Match> ret = new ArrayList<>();
    while (!buyOrders.isEmpty() && buyOrders.lastKey() != null && order.getNShares() > 0) {
      Queue<Order> orderQueue = buyOrders.lastEntry().getValue();
      while (!orderQueue.isEmpty() && order.getNShares() > 0) {
        ret.add(Match.match(
            order,
            orderQueue.element(),
            orderQueue.element().getPrice(),
            Math.min(order.getNShares(), orderQueue.element()
                .getNShares())));
        if (orderQueue.element().getNShares() == 0) {
          orderQueue.remove();
        }
      }
      if (orderQueue.isEmpty()) {
        buyOrders.remove(buyOrders.lastKey());
      }
    }
    if (order.getNShares() > 0) {
      bestMarketPriceSellOrders.add(order);
    }
    return ret;
  }

  private List<Match> addLimitSellOrder(Order order) {
    float price = order.getPrice();
    List<Match> ret = new ArrayList<>();
    // first, match with best market price orders
    while (!bestMarketPriceBuyOrders.isEmpty() && order.getNShares() > 0) {
      ret.add(Match.match(
          order,
          bestMarketPriceBuyOrders.element(),
          price,
          Math.min(order.getNShares(), bestMarketPriceBuyOrders.element()
              .getNShares())));
      if (bestMarketPriceBuyOrders.element().getNShares() == 0) {
        bestMarketPriceBuyOrders.remove();
      }
    }
    // then match with limit orders
    while (!buyOrders.isEmpty() && buyOrders.lastKey() != null && buyOrders.lastKey() >= price
        && order.getNShares() > 0) {
      Queue<Order> orderQueue = buyOrders.lastEntry().getValue();
      while (!orderQueue.isEmpty() && order.getNShares() > 0) {
        ret.add(Match.match(
            order,
            orderQueue.element(),
            orderQueue.element().getPrice(),
            Math.min(order.getNShares(), orderQueue.element()
                .getNShares())));
        if (orderQueue.element().getNShares() == 0) {
          orderQueue.remove();
        }
      }
      if (orderQueue.isEmpty()) {
        buyOrders.remove(buyOrders.lastKey());
      }
    }
    if (order.getNShares() > 0) {
      Queue<Order> orderQueue = sellOrders.get(price);
      if (orderQueue != null) {
        orderQueue.add(order);
      } else {
        orderQueue = new LinkedList<>();
        orderQueue.add(order);
        sellOrders.put(price, orderQueue);
      }
    }
    return ret;
  }
  
}
